package edu.gatech.cc.liam.geometry.linear.programming;

import java.util.Arrays;

import org.gnu.glpk.GLPK;
import org.gnu.glpk.GLPKConstants;
import org.gnu.glpk.SWIGTYPE_p_double;
import org.gnu.glpk.SWIGTYPE_p_int;
import org.gnu.glpk.glp_iocp;
import org.gnu.glpk.glp_prob;
import org.gnu.glpk.glp_smcp;

import edu.gatech.cc.liam.geometry.linear.FeasibleSet;
import edu.gatech.cc.liam.geometry.linear.Halfspace;
import edu.gatech.cc.liam.geometry.linear.NPoint;
import edu.gatech.cc.liam.geometry.linear.Polytope;

public class BinaryProb {
	// see the GNU Linear Programming Kit Reference Manual
	// http://bjoern.dapnet.de/glpk/refman48.pdf
	// for help on GLPK
	
	private glp_prob theLP;
	private glp_iocp parm;
	public final int numConstraints;
	public final int numVariables_;
		
	private LPSolution lastSol;
	private int numIterations;
	
	/**
	 * Instantiates a new linear prob sol.
	 * 
	 * @param optimalPoint
	 *          the optimal point
	 * @param optimalValue
	 *          the optimal value
	 */
	public BinaryProb(int numVariables, int numConstraints) {
		super();
		this.numConstraints = numConstraints;
		this.numVariables_ = numVariables;
		//initLP();
	}
	
	public BinaryProb(FeasibleSet fs) {
		super();
		this.numConstraints = fs.inequalities.size() + fs.equalities.size();
		this.numVariables_ = fs.getDimensions();
		initLP();
		setColBounds(fs.lb, fs.ub);
		Halfspace[] ieA = new Halfspace[fs.inequalities.size()];
		Halfspace[] eA = new Halfspace[fs.equalities.size()];
		setFeasibleReigion(fs.inequalities.toArray(ieA), fs.equalities.toArray(eA));
	}
	
	private void initLP() {
		//System.out.println(System.getProperty("java.library.path"));
		theLP = GLPK.glp_create_prob();
		parm = new glp_iocp();
        GLPK.glp_init_iocp(parm);
        parm.setMsg_lev(GLPK.GLP_MSG_OFF);
        parm.setPresolve(GLPK.GLP_ON);
		//GLPK._glp_lpx_set_int_parm(theLP, GLPKConstants.LPX_K_MSGLEV, GLPK.GLP_MSG_OFF);
		//theLP.setHook(null);
		//theLP.enablePrints(false);
		GLPK.glp_set_obj_dir(theLP, GLPKConstants.GLP_MAX);
		GLPK.glp_add_rows(theLP, numConstraints);
		GLPK.glp_add_cols(theLP, numVariables_);
		for(int i=0; i<numVariables_; i++) {
			GLPK.glp_set_col_kind(theLP, i+1, GLPKConstants.GLP_BV);
		}
	}

	static int numRuns = 0;

	public void deleteProblem() {
		parm.delete();
		GLPK.glp_delete_prob(theLP);
	}

	public void initMultiProblem(Polytope theIneq) {
		initMultiProblem(theIneq, Double.NaN);
	}
	public void initMultiProblem(Polytope theIneq, double bound) {
		initMultiProblem(theIneq, new NPoint(theIneq.getDimensions(), -bound),
								  new NPoint(theIneq.getDimensions(), bound));
	}
	public void initMultiProblem(Polytope theIneq, NPoint lb, NPoint ub) {
		initLP();
		setColBounds(lb, ub);
		setFeasibleReigion(theIneq.toArray(), new Halfspace[0]);
	}

	public boolean findBoolSol(double[] theObj) {
		setObjective(theObj);
		GLPK._glp_lpx_set_int_parm(theLP, GLPKConstants.LPX_K_ITCNT, 0);
		int ret = GLPK.glp_intopt(theLP, parm);
		if (! (ret == 0)) { //(ret == GLPKConstants.LPX_E_OK || ret == GLPKConstants.LPX_E_SING)) {
			parm.setMsg_lev(GLPK.GLP_MSG_DBG);
			ret = GLPK.glp_intopt(theLP, parm);
		}
		if(ret != 0)
			throw new Error("Error : bad LP return value");

		this.lastSol = new LPSolution(GLPK.glp_mip_obj_val(theLP), retrieveOptimalPoint());
		this.numIterations = GLPK._glp_lpx_get_int_parm(theLP, GLPKConstants.LPX_K_ITCNT);
		return (GLPK.glp_mip_status(theLP) == GLPK.GLP_OPT);
	}
	
	public BasicSolution getCurrentBasis() {
		BasicSolution r = new BasicSolution();
		int numRows = GLPK.glp_get_num_rows(theLP);
		r.rowStats = new int[numRows];
		int numCols = GLPK.glp_get_num_cols(theLP);
		r.colStats = new int[numCols];
		for(int i=0; i<numRows; i++) {
			r.rowStats[i] = GLPK.glp_get_row_stat(theLP, i+1);
		}
		for(int i=0; i<numCols; i++) {
			r.colStats[i] = GLPK.glp_get_col_stat(theLP, i+1);
		}
		return r;
	}
	public void restoreBasis(BasicSolution basis) {
		int numRows = GLPK.glp_get_num_rows(theLP);
		int numCols = GLPK.glp_get_num_cols(theLP);
		assert basis.colStats.length == numCols;
		assert basis.rowStats.length == numRows;
		//assert basis.getNumBasis() == numRows;
		for(int i=0; i<basis.rowStats.length; i++) {
			//if(basis.rowStats[i] == GLPKConstants.GLP_BS);
				GLPK.glp_set_row_stat(theLP, i+1, basis.rowStats[i]);
		}
		for(int i=0; i<basis.colStats.length; i++) {
			//if(basis.colStats[i] == GLPKConstants.GLP_BS);
				GLPK.glp_set_col_stat(theLP, i+1, basis.colStats[i]);
		}
		//if(basis.isPrimal)
	   //     parm.setMeth(GLPKConstants.GLP_DUALP);
		//else
	    //    parm.setMeth(GLPKConstants.GLP_PRIMAL);
	}

	private void setColBounds(NPoint lb, NPoint ub) {
		for (int col = 0; col < numVariables_; col++) {
			if(!Double.isNaN(lb.values[col]) && !Double.isNaN(lb.values[col])) {
				assert(lb.values[col] <= ub.values[col]);
				GLPK.glp_set_col_bnds(theLP, col + 1,
						GLPKConstants.GLP_DB, lb.values[col], ub.values[col]);
			} else if(Double.isNaN(lb.values[col]) && Double.isNaN(lb.values[col])) {
				GLPK.glp_set_col_bnds(theLP, col + 1, GLPKConstants.GLP_FR, Double.NaN, Double.NaN);
			} else {
				assert(false);
				// TODO: ONE SIDED BOUNDS NOT IMPLEMENTED YET
			}
		}
	}
	
	private void setFeasibleReigion(Halfspace[] theIneq, Halfspace[] theEquals) {
		assert(numConstraints == theIneq.length + theEquals.length);
		assert((theIneq.length == 0) || (numVariables_ == theIneq[0].normal.length));
		assert((theEquals.length == 0) || (numVariables_ == theEquals[0].normal.length));
		
		for (int row = 0; row < theIneq.length; row++) {
			GLPK.glp_set_row_bnds(theLP, row + 1, GLPKConstants.GLP_UP, Double.NaN, -theIneq[row].offset);
		}
		for (int row = 0; row < theEquals.length; row++) {
			GLPK.glp_set_row_bnds(theLP, theIneq.length + row + 1, 
								  GLPKConstants.GLP_FX, -theEquals[row].offset, -theEquals[row].offset);
		}
		final int maxNumEntries = numConstraints * numVariables_;
		final SWIGTYPE_p_int indexInequNum = GLPK.new_intArray(maxNumEntries + 1);
		final SWIGTYPE_p_int indexVar = GLPK.new_intArray(maxNumEntries + 1);
		final SWIGTYPE_p_double theVals = GLPK.new_doubleArray(maxNumEntries + 1);
		int i = 1;
		// note: row = inequNum
		// note: col = varNum
		for (int row = 0; row < theIneq.length; row++) {
			for (int var = 0; var < numVariables_; var++) {
				if (theIneq[row].normal[var] == 0.0) {
					continue;
				}
				assert i<maxNumEntries+1;
				GLPK.intArray_setitem(indexInequNum, i, row + 1);
				GLPK.intArray_setitem(indexVar, i, var + 1);
				GLPK.doubleArray_setitem(theVals, i, theIneq[row].normal[var]);
				i++;
			}
		}
		for (int row = 0; row < theEquals.length; row++) {
			for (int var = 0; var < numVariables_; var++) {
				if (theEquals[row].normal[var] == 0.0) {
					continue;
				}
				assert i<maxNumEntries+1;
				GLPK.intArray_setitem(indexInequNum, i, theIneq.length + row + 1);
				GLPK.intArray_setitem(indexVar, i, var + 1);
				GLPK.doubleArray_setitem(theVals, i, theEquals[row].normal[var]);
				i++;
			}
		}
		GLPK.glp_load_matrix(theLP, i - 1, indexInequNum, indexVar, theVals);
		GLPK.delete_intArray(indexInequNum);
		GLPK.delete_intArray(indexVar);
		GLPK.delete_doubleArray(theVals);
	}

	private void setObjective(double[] theObj) {
		assert(numVariables_ == theObj.length);
		for (int i = 0; i < theObj.length; i++) {
			GLPK.glp_set_obj_coef(theLP, i + 1, theObj[i]);
		}
	}
	public LPSolution getLastSolutionClone() {
		return lastSol.clone();
	}
	
	private double[] retrieveOptimalPoint() {
		final double[] optimalPoint = new double[numVariables_];
		for (int k = 0; k < optimalPoint.length; k++) {
			optimalPoint[k] = GLPK.glp_mip_col_val(theLP, k + 1);
		}
		return optimalPoint;
	}
	public int getNumIterations() {
		return this.numIterations;
	}
	public double[] getOptimalPoint() {
		return this.lastSol.optPt;
	}
	public double getOptimalValue() {
		return this.lastSol.optValue;
	}
	
	public void printResult() {
		System.out.println("Solved LP in " + getNumIterations() + " iterations");
		System.out.println("   Solution: " + lastSol.optValue + "  at: "
							+ Arrays.toString(lastSol.optPt));
	}
	
	public static void main(String[] args) {

	}
	

}
